﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.ServiceProcess;

namespace PickGold.Applications
{
	/// <summary/>
	public class TcpProxy : EventArgs, IDisposable
	{
		#region inner class(Service,TcpEndPoint)

		class Service : ServiceBase
		{
			public TcpListener[] Listeners;

			protected override void OnStart(string[] args)
			{
				if (args == null || args.Length == 0)
				{
					var c = int.Parse(Environment.GetEnvironmentVariable("/-", EnvironmentVariableTarget.Process));
					args = new string[c];
					for (var i = 0; i < c; i++)
						args[i] = Environment.GetEnvironmentVariable("/" + i, EnvironmentVariableTarget.Process);
					//c = 0;
				}
				if (LogFile != null)
					WriteLog("Service Start(" + Environment.GetEnvironmentVariable("/-", EnvironmentVariableTarget.Process) + "/" + args.Length + "). " + string.Join(" ", args));
				this.Listeners = TcpProxy.GetListeners(args);
				if (this.Listeners == null)
				{
					this.ExitCode = Environment.ExitCode;
					this.Stop();
				}

				base.OnStart(args);
			}

			protected override void OnPause()
			{
				if (LogFile != null)
					WriteLog("Service Pause.");
				TcpProxy.PWH.Reset();
				base.OnPause();
			}

			protected override void OnContinue()
			{
				if (LogFile != null)
					WriteLog("Service Continue.");
				TcpProxy.PWH.Set();
				base.OnContinue();
			}

			protected override void OnStop()
			{
				if (LogFile != null)
					WriteLog("Service Stop.");
				if (this.Listeners != null)
				{
					foreach (var tl in this.Listeners)
						tl.Stop();
					this.Listeners = null;
				}
				base.OnStop();
			}

			protected override void OnShutdown()
			{
				if (LogFile != null)
					WriteLog("Service Shutdown.");
				this.Stop();
				base.OnShutdown();
			}
		}

		class TcpEndPoint : IPEndPoint
		{
			public string Pattern;

			public TcpEndPoint(string pattern, IPAddress address, int port) : base(address, port) { this.Pattern = pattern; }
			public TcpEndPoint(string pattern, long address, int port) : base(address, port) { this.Pattern = pattern; }

			/// <summary>
			/// 匹配
			/// </summary>
			/// <param name="input"></param>
			/// <returns></returns>
			public bool IsMatch(string input)
			{
				if (string.IsNullOrWhiteSpace(input) || string.IsNullOrWhiteSpace(this.Pattern))
					return false;

				try
				{
					return Regex.IsMatch(input, this.Pattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
				}
				input = "Invalid pattern: " + this.Pattern;
				if (LogFile != null)
					WriteLog(input);
				if (LogOut != null)
					WriteLine(input);
				this.Pattern = null;
				return false;
			}

			/// <summary>
			/// 匹配
			/// </summary>
			/// <param name="me"></param>
			/// <param name="input"></param>
			/// <returns></returns>
			public static bool IsMatch(IPEndPoint me, string input)
			{
				var p = me as TcpEndPoint;
				if (p == null || string.IsNullOrWhiteSpace(input) || string.IsNullOrWhiteSpace(p.Pattern))
					return false;

				try
				{
					return Regex.IsMatch(input, p.Pattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
				}
				input = "Invalid pattern: " + p.Pattern;
				if (LogFile != null)
					WriteLog(input);
				if (LogOut != null)
					WriteLine(input);
				p.Pattern = null;
				return false;
			}

			/// <summary>
			/// 0：正常；1：主机缺失；2：端口缺失；负：有错；另：全部缺失
			/// </summary>
			/// <param name="input"></param>
			/// <param name="value"></param>
			/// <returns></returns>
			public static int TryParse(string input, ref IPEndPoint value)
			{
				if (string.IsNullOrWhiteSpace(input))
				{
					value = null;
					return -2;
				}

				var i = input.IndexOf(':');
				if (i < 0)
				{
					value = null;
					return -1;
				}

				var h = input.Remove(i);
				var p = i + 1 >= input.Length ? string.Empty : input.Substring(i + 1);
				var a = value.Address;
				var v = 0;
				try
				{
					if (string.IsNullOrWhiteSpace(h))
					{
						v += 1;
					}
					else if (Regex.IsMatch(h, "[a-z]", RegexOptions.Compiled))
					{
						a = Dns.GetHostAddresses(h)[0];
					}
					else if (!IPAddress.TryParse(h, out a))
					{
						value = null;
						return -16;
					}
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
					value = null;
					return -8;
				}

				if (string.IsNullOrWhiteSpace(p))
				{
					v += 2;
					i = value.Port;
				}
				else if (!int.TryParse(p, out i))
				{
					value = null;
					return -32;
				}
				else if (i <= IPEndPoint.MinPort || i > IPEndPoint.MaxPort)
				{
					value = null;
					return -64;
				}

				value = new IPEndPoint(a, i);
				return v;
			}
		}

		#endregion

		/// <summary/>
		public const int BUFFER = ushort.MaxValue + 1;//65536

		/// <summary/>
		private readonly static byte[] H200 = Encoding.UTF8.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
		/// <summary/>
		private readonly static byte[] H407 = Encoding.UTF8.GetBytes("HTTP/1.1 407 Unauthorized\r\n\r\n");
		/// <summary/>
		private readonly static Dictionary<string, IPEndPoint> PS = new Dictionary<string, IPEndPoint>();
		/// <summary/>
		private readonly static ManualResetEvent PWH = new ManualResetEvent(true);

		/// <summary/>
		public static TextWriter LogOut;
		/// <summary/>
		public static TextWriter LogError;
		/// <summary/>
		public static string LogFile;
		/// <summary/>
		public static EventHandler EventConnect;
		/// <summary/>
		public static EventHandler EventHttp;
		/// <summary/>
		public static EventHandler EventFtp;
		/// <summary/>
		public static EventHandler EventOther;

		/// <summary/>
		public string Account;
		/// <summary/>
		public DateTime Timestamp;
		/// <summary/>
		public DateTime Overdue;
		/// <summary/>
		public long Limit;
		/// <summary/>
		public int ClientRead;
		/// <summary/>
		public int ClientWait;
		/// <summary/>
		public long ClientCount;
		/// <summary/>
		public byte[] ClientBuffer;
		/// <summary/>
		public TcpClient ClientConnect;
		/// <summary/>
		public NetworkStream ClientStream;
		/// <summary/>
		public int RemoteRead;
		/// <summary/>
		public int RemoteWait;
		/// <summary/>
		public long RemoteCount;
		/// <summary/>
		public byte[] RemoteBuffer;
		/// <summary/>
		public TcpClient RemoteConnect;
		/// <summary/>
		public NetworkStream RemoteStream;

		/// <summary/>
		public TcpProxy(TcpClient client)
		{
			this.Timestamp = DateTime.UtcNow;
			if (client == null)
				return;

			this.ClientConnect = client;
			this.ClientStream = client.GetStream();
			this.ClientBuffer = new byte[BUFFER];
		}

		/// <summary/>
		public void EndClientRead(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(this.EndClientRead, result))
				return;

			if (this.ClientStream == null)
				return;

			var s = this.ClientBuffer;
			if (result != null)
			{
				s = result.AsyncState as byte[];
				try
				{
					var i = this.ClientStream.EndRead(result);
					this.ClientRead--;
					if (i > 0)
					{
						if (LogOut != null)
							WriteLine("Receive from client(" + this.ClientConnect.Client.RemoteEndPoint + ") " + i + " / " + this.ClientCount);
						this.ClientWait = 0;
						this.ClientCount += i;
						this.RemoteStream.Write(s, 0, i);
						if ((this.Overdue > DateTime.MinValue && DateTime.Now > this.Overdue) ||
							(this.Limit > 0L && this.ClientCount + this.RemoteCount > this.Limit))
						{
							if (LogOut != null)
								WriteLine("Overdue Or Overflow.");
							this.Dispose(false);
							return;
						}
					}
					else
					{
						if (++this.ClientWait > byte.MaxValue)
						{
							if (LogOut != null)
								WriteLine("Waiting client(" + this.ClientConnect.Client.RemoteEndPoint + ") timeout" + this.ClientWait);
							this.Dispose(false);
							return;
						}

						Thread.Sleep(this.ClientWait * 100);
					}
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
					this.Dispose(false);
					return;
				}
			}
			if (this.ClientRead > 0)
				return;

			if (s == null)
			{
				s = new byte[BUFFER];
				this.ClientBuffer = s;
			}
			try
			{
				this.ClientStream.BeginRead(s, 0, BUFFER, this.EndClientRead, s);
				this.ClientRead++;
				return;
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
			}
			this.Dispose(false);
		}

		/// <summary/>
		public void EndRemoteRead(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(this.EndRemoteRead, result))
				return;

			var s = this.RemoteBuffer;
			if (result != null)
			{
				s = result.AsyncState as byte[];
				try
				{
					var i = this.RemoteStream.EndRead(result);
					this.RemoteRead--;
					if (i > 0)
					{
						if (LogOut != null)
							WriteLine("Receive from remote(" + this.RemoteConnect.Client.RemoteEndPoint + ") " + i + " / " + this.RemoteCount);
						this.RemoteWait = 0;
						this.RemoteCount += i;
						this.ClientStream.Write(s, 0, i);
						if ((this.Overdue > DateTime.MinValue && DateTime.Now > this.Overdue) ||
							(this.Limit > 0L && this.ClientCount + this.RemoteCount > this.Limit))
						{
							if (LogOut != null)
								WriteLine("Overdue Or Overflow.");
							this.Dispose(false);
							return;
						}
					}
					else
					{
						if (++this.RemoteWait > byte.MaxValue)
						{
							if (LogOut != null)
								WriteLine("Waiting remote(" + this.RemoteConnect.Client.RemoteEndPoint + ") timeout" + this.RemoteWait);
							this.Dispose(false);
							return;
						}

						Thread.Sleep(this.RemoteWait * 100);
					}
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
					this.Dispose(false);
					return;
				}
			}
			if (this.RemoteRead > 0)
				return;

			if (s == null)
			{
				s = new byte[BUFFER];
				this.RemoteBuffer = s;
			}
			try
			{
				this.RemoteStream.BeginRead(s, 0, BUFFER, this.EndRemoteRead, s);
				this.RemoteRead++;
				return;
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
			}
			this.Dispose(false);
		}

		/// <summary/>
		public bool Decrypt(string cipher)
		{
			return this.Decrypt(new Guid(cipher));
		}
		/// <summary/>
		public bool Decrypt(Guid cipher)
		{
			var t = cipher.ToString("N");

			var a = long.Parse(t.Remove(2), NumberStyles.HexNumber);
			var b = long.Parse(t.Substring(2, 2), NumberStyles.HexNumber);
			var c = long.Parse(t.Substring(4, 2), NumberStyles.HexNumber);
			var d = long.Parse(t.Substring(6, 2), NumberStyles.HexNumber);

			var yy = int.Parse(t.Substring(8, 4), NumberStyles.HexNumber) ^ int.Parse(t.Substring(20, 4), NumberStyles.HexNumber);
			var mm = int.Parse(t.Substring(12, 2), NumberStyles.HexNumber) ^ int.Parse(t.Substring(24, 2), NumberStyles.HexNumber);
			var dd = int.Parse(t.Substring(14, 2), NumberStyles.HexNumber) ^ int.Parse(t.Substring(26, 2), NumberStyles.HexNumber);
			var hh = int.Parse(t.Substring(16, 2), NumberStyles.HexNumber) ^ int.Parse(t.Substring(28, 2), NumberStyles.HexNumber);
			var nn = int.Parse(t.Substring(18, 2), NumberStyles.HexNumber) ^ int.Parse(t.Substring(30, 2), NumberStyles.HexNumber);

			var i = (a << 40) + (b << 32) + (c << 24) + (d << 16) + (a << 8) + c;
			var l = long.Parse(t.Substring(20), NumberStyles.HexNumber) ^ i;

			var dt = DateTime.MinValue;
			if (DateTime.TryParse(yy + "-" + mm + "-" + dd + " " + hh + ":" + nn + ":00", out dt) && dt > DateTime.Now)
			{
				this.Account = string.Empty;
				if (a > 32 && a < 127)
					this.Account = this.Account + (char)a;
				if (b > 32 && b < 127)
					this.Account = this.Account + (char)b;
				if (c > 32 && c < 127)
					this.Account = this.Account + (char)c;
				if (d > 32 && d < 127)
					this.Account = this.Account + (char)d;
				this.Overdue = dt;
				this.Limit = l;
				return true;
			}

			return false;
		}

		/// <summary/>
		public void Dispose(bool disposing)
		{
			var t = "Disposied; start at " + this.Timestamp + "; Upload: " + this.ClientCount + "; Download: " + this.RemoteCount;
			if (this.ClientStream != null)
				this.ClientStream.Close();
			this.ClientStream = null;
			if (this.ClientConnect != null)
			{
				try
				{
					if (this.ClientConnect.Client != null)
						t = this.ClientConnect.Client.RemoteEndPoint + " " + t;
				}
				catch (Exception x)
				{
					t = x.Message + " " + t;
				}
				this.ClientConnect.Close();
				this.ClientConnect = null;
			}
			this.ClientBuffer = null;

			if (this.RemoteStream != null)
				this.RemoteStream.Close();
			this.RemoteStream = null;
			if (this.RemoteConnect != null)
				this.RemoteConnect.Close();
			this.RemoteConnect = null;
			this.RemoteBuffer = null;

			if (LogOut != null)
				WriteLine(t);
			if (string.IsNullOrWhiteSpace(this.Account))
			{
				if (LogFile != null)
					WriteLog(t);
				return;
			}

			if (LogFile != null)
				WriteLog(this.Account + " Disposied; start at " + this.Timestamp + "; Upload: " + this.ClientCount + "; Download: " + this.RemoteCount + Environment.NewLine + t);
			if (!Monitor.TryEnter(PS, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(PS);
			try
			{
				var l = this.ClientCount + this.RemoteCount;
				try
				{
					t = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, this.Account + ".ini");
					if (File.Exists(t))
						l += long.Parse(File.ReadAllText(t));
					File.WriteAllText(t, l.ToString());
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
				}
			}
			finally
			{
				Monitor.Exit(PS);
			}
			this.Account = null;
		}

		void IDisposable.Dispose()
		{
			this.Dispose(true);
		}

		private static Guid Encrypt(params string[] args)
		{
			var user = string.Empty;
			var over = DateTime.MinValue;
			var limit = 0L;
			for (var i = args.Length - 1; i >= 0; i--)
			{
				var t = args[i];
				if (string.IsNullOrWhiteSpace(t))
					continue;

				if (t[0] == '/')
					t = t.Substring(1);
				if (t.StartsWith("user=", StringComparison.OrdinalIgnoreCase))
					user = t.Substring(5);
				else if (t.StartsWith("over=", StringComparison.OrdinalIgnoreCase))
					DateTime.TryParse(t.Substring(5), out over);
				else if (t.StartsWith("limit=", StringComparison.OrdinalIgnoreCase))
					long.TryParse(t.Substring(6), out limit);
			}
			if (string.IsNullOrWhiteSpace(user) && over == DateTime.MinValue && limit == 0L)
				return Guid.Empty;

			if (string.IsNullOrWhiteSpace(user))
			{
				Console.WriteLine("Agruments [/user=?] not found or invalid");
				return Guid.Empty;
			}

			if (over < DateTime.Now)
			{
				Console.WriteLine("Agruments [/over=?] not found or invalid");
				return Guid.Empty;
			}

			if (limit < 0L)
			{
				Console.WriteLine("Agruments [/limit=?] not found or invalid");
				return Guid.Empty;
			}

			return Encrypt(user, over, limit);
		}

		private static Guid Encrypt(string account, DateTime overdue, long limit)
		{
			var a = (account.Length > 0 ? (long)account[0] : 0L);
			var b = (account.Length > 1 ? (long)account[1] : 0L);
			var c = (account.Length > 2 ? (long)account[2] : 0L);
			var d = (account.Length > 3 ? (long)account[3] : 0L);

			var i = (a << 40) + (b << 32) + (c << 24) + (d << 16) + (a << 8) + c;
			var j = limit ^ i;
			var v = j.ToString("X").PadLeft(16, '0').Substring(4);

			var y = overdue.Year ^ int.Parse(v.Remove(4), NumberStyles.HexNumber);
			var m = overdue.Month ^ int.Parse(v.Substring(4, 2), NumberStyles.HexNumber);
			var t = overdue.Day ^ int.Parse(v.Substring(6, 2), NumberStyles.HexNumber);
			var u = overdue.Hour ^ int.Parse(v.Substring(8, 2), NumberStyles.HexNumber);
			var n = overdue.Minute ^ int.Parse(v.Substring(10), NumberStyles.HexNumber);

			v = a.ToString("X").PadLeft(2, '0') + b.ToString("X").PadLeft(2, '0') + c.ToString("X").PadLeft(2, '0') + d.ToString("X").PadLeft(2, '0') +
				y.ToString("X").PadLeft(4, '0') + m.ToString("X").PadLeft(2, '0') + t.ToString("X").PadLeft(2, '0') + u.ToString("X").PadLeft(2, '0') + n.ToString("X").PadLeft(2, '0') +
				v;//{9E915AB2-5B7D-4BBC-B028-EE7A50115898}
			WriteLine(v);
			return new Guid(v);
		}

		private static TcpListener[] GetListeners(params string[] args)
		{
			if (args == null || args.Length == 0)
			{
				var t = "/connect=10.161.145.167:61194/[^a-z]+C[^a-z]*O[^a-z]*N[^a-z]*N[^a-z]*E[^a-z]*C[^a-z]*T[^a-z]+ /http=10.161.145.167 /ftp=erp.51760.com:50042 /other=3389 /56778 8080";
				t = t + Environment.NewLine + "/user=King /over=2081-03-01T12:34:56 /limit=2147483647";
				if (LogFile != null)
					WriteLog(t);
				if (LogOut != null)
					WriteLine(t);
				Environment.ExitCode = 0;
				return null;
			}

			if (Encrypt(args) != Guid.Empty)
				return null;

			int n;
			IPAddress a;
			TcpListener[] ts;
			TcpEndPoint cp = null;
			TcpEndPoint hp = null;
			TcpEndPoint fp = null;
			TcpEndPoint op = null;
			var ps = new List<TcpEndPoint>();
			for (var i = args.Length - 1; i >= 0; i--)
			{
				if (string.IsNullOrWhiteSpace(args[i]))
					continue;

				var m = Regex.Match(args[i], @"^/*([^=]+=)?(?:([^\:\r\n\t\s ,=/]+)[\:\r\n\t\s ,]+)?([0-9]+)(/.*)?$", RegexOptions.Compiled);
				if (!m.Success)
				{
					m = Regex.Match(args[i], @"^/*([^=]+=)?([^\:\r\n\t\s ,=/]+)(?:[\:\r\n\t\s ,]+([0-9]+))?(/.*)?$", RegexOptions.Compiled);
					if (!m.Success)
					{
						var t = "Invalid parameter " + args[i];
						if (LogFile != null)
							WriteLog(t);
						if (LogOut != null)
							WriteLine(t);
						Environment.ExitCode = 1;
						return null;
					}
				}

				var k = m.Groups[1].Value.ToLower();
				var h = m.Groups[2].Value.ToLower();
				var p = m.Groups[3].Value;
				var f = m.Groups[4].Value;
				if (string.IsNullOrWhiteSpace(p))
				{
					n = TcpEndPoint.MinPort;
				}
				else if (!int.TryParse(p, out n) || n < TcpEndPoint.MinPort || n > TcpEndPoint.MaxPort)
				{
					var t = "Invalid parameter " + args[i];
					if (LogFile != null)
						WriteLog(t);
					if (LogOut != null)
						WriteLine(t);
					Environment.ExitCode = 5;
					return null;
				}

				try
				{
					if (string.IsNullOrWhiteSpace(h))
						a = string.IsNullOrWhiteSpace(k) ? IPAddress.Any : IPAddress.Loopback;
					else if (Regex.IsMatch(h, "[a-z]", RegexOptions.Compiled))
						a = Dns.GetHostAddresses(h)[0];
					else
						IPAddress.TryParse(h, out a);
				}
				catch (Exception x)
				{
					var t = "Invalid parameter " + args[i];
					if (LogError != null)
						WriteError(x);
					if (LogFile != null)
						WriteLog(t);
					if (LogOut != null)
						WriteLine(t);
					Environment.ExitCode = 15;
					return null;
				}

				if (a == null)
				{
					var t = "Invalid parameter " + args[i];
					if (LogFile != null)
						WriteLog(t);
					if (LogOut != null)
						WriteLine(t);
					Environment.ExitCode = 20;
					return null;
				}

				if (!string.IsNullOrWhiteSpace(f))
					f = f.Substring(1);
				if (string.Compare(k, "connect=", StringComparison.OrdinalIgnoreCase) == 0)
				{
					if (n == TcpEndPoint.MinPort)
						n = 1194;
					cp = new TcpEndPoint(f, a, n);
					continue;
				}

				if (string.Compare(k, "http=", StringComparison.OrdinalIgnoreCase) == 0)
				{
					if (n == TcpEndPoint.MinPort)
						n = 80;
					hp = new TcpEndPoint(f, a, n);
					continue;
				}

				if (string.Compare(k, "ftp=", StringComparison.OrdinalIgnoreCase) == 0)
				{
					if (n == TcpEndPoint.MinPort)
						n = 21;
					fp = new TcpEndPoint(f, a, n);
					continue;
				}

				if (string.Compare(k, "other=", StringComparison.OrdinalIgnoreCase) == 0 ||
					string.Compare(k, "proxy=", StringComparison.OrdinalIgnoreCase) == 0)
				{
					if (n == TcpEndPoint.MinPort)
						n = 3389;
					op = new TcpEndPoint(f, a, n);
					continue;
				}

				if (n == TcpEndPoint.MinPort)
					n = 3128;
				foreach (var ep in ps)
				{
					if (ep.Address == a && ep.Port == n)
					{
						a = null;
						n = TcpEndPoint.MinPort;
						break;
					}
				}
				if (a != null)
					ps.Add(new TcpEndPoint(f, a, n));
			}
			if (cp == null && hp == null && fp == null && op == null)
			{
				args = null;
				return GetListeners(args);
			}

			n = ps.Count;
			if (ps.Count == 0)
			{
				ps.Add(new TcpEndPoint(null, IPAddress.Any, 8080));
				n = 1;
			}
			ts = new TcpListener[n];
			foreach (var p in ps)
			{
				var tl = new TcpListener(p);
				tl.Start();
				TcpProxy.Proxy(tl, cp, hp, fp, op);
				ts[--n] = tl;
			}
			Environment.ExitCode = 0;
			return ts;
		}

		private static IPEndPoint ProxyConnect(TcpProxy tp, IPEndPoint ep, string head)
		{
			var h = EventConnect;
			if (h != null)
			{
				if (LogFile != null)
					WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " " + h.Method);
				h(tp.ClientConnect, tp);
				return null;
			}

			var ms = Regex.Matches(head, @"[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}", RegexOptions.Compiled | RegexOptions.Multiline);//{4B696E67-4C88-17F3-234D-4B6910F62A7B}
			for (var i = ms.Count - 1; i >= 0; i--) //账号  年              月日            时分            各小区段异或值与流量上限
			{
				if (tp.Decrypt(ms[i].Value))// KingXXXX-2017-1231-2359-925031271132 //4B696E67-4C8B-17F3-234C-4B6910F62A7B//4B696E67-4C8A-17F3-234C-4B6910F62A7B
				{
					ms = null;
					break;
				}
			}
			if (ms != null)
			{
				if (LogFile != null)
					WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " Unauthorized.");
				try
				{
					tp.ClientStream.Write(H407, 0, H407.Length);
					tp.ClientStream.Flush();
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
				}
				tp.Dispose(false);
				return null;
			}

			var f = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, tp.Account + ".ini");
			if (File.Exists(f))
			{
				if (!Monitor.TryEnter(PS, Fiber.LockTimeout))
					Fiber.ThrowLockTimeoutException(PS);
				try
				{
					tp.Limit -= long.Parse(File.ReadAllText(f));
				}
				finally
				{
					Monitor.Exit(PS);
				}
			}
			ms = Regex.Matches(head, @"^.+-Via: ([^\:]+)\:([0-9]+)$", RegexOptions.Compiled | RegexOptions.Multiline);
			for (var i = ms.Count - 1; i >= 0; i--)
			{
				var p = ep;
				if (TcpEndPoint.TryParse(ms[i].Groups[1].Value + ":" + ms[i].Groups[2].Value, ref p) <= 2 && p != null)
				{
					if (LogFile != null)
						WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " via " + p);
					ep = p;
					break;
				}
			}
			head = tp.ClientConnect.Client.RemoteEndPoint + "(" + tp.Account + "/" + tp.Overdue + "/" + tp.Limit + ") Connect " + ep;
			if (LogFile != null)
				WriteLog(head);
			if (LogOut != null)
				WriteLine(head);
			return ep;
		}

		private static IPEndPoint ProxyHttp(TcpProxy tp, IPEndPoint ep, string head)
		{
			var h = EventHttp;
			var m = Regex.Match(head, @"^[a-zA-Z]+[\r\n\t\s ]/([^\?\:\+\r\n\t\s /=@,]+)[\.\:\+\r\n\t\s /=@,]+([0-9]+)[^\?]*\?\1=\2", RegexOptions.Compiled);// http://h/主机或地址:端口/?主机或地址=端口&主机或地址为纯数字则表示清空还原重定向
			if (m.Success)
			{
				var p = ep;
				if (TcpEndPoint.TryParse(m.Groups[1].Value + ":" + m.Groups[2].Value, ref p) <= 2 && p != null)
				{
					ep = p;
					p = tp.ClientConnect.Client.RemoteEndPoint as IPEndPoint;
					if (!Monitor.TryEnter(PS, Fiber.LockTimeout))
						Fiber.ThrowLockTimeoutException(PS);
					try
					{
						PS[p.Address.ToString()] = ep;
					}
					finally
					{
						Monitor.Exit(PS);
					}
					head = "HTTP/1.1 204 No Content\r\n\r\n";
					tp.ClientBuffer = Encoding.UTF8.GetBytes(head);
					tp.ClientStream.Write(tp.ClientBuffer, 0, tp.ClientBuffer.Length);
					tp.ClientStream.Flush();
					if (LogFile != null)
						WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " Redirect " + p);
					tp.Dispose(false);
					return null;
				}

				head = tp.ClientConnect.Client.RemoteEndPoint + " Http " + ep;
				if (LogFile != null)
					WriteLog(head);
				if (LogOut != null)
					WriteLine(head);
				return ep;
			}

			if (h != null)
			{
				if (LogFile != null)
					WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " " + h.Method);
				h(tp.ClientConnect, tp);
				return null;
			}

			m = Regex.Match(head, @"^.+-Via: ([^\:]+)\:([0-9]+)$", RegexOptions.Compiled | RegexOptions.Multiline);
			if (m.Success)
			{
				var p = ep;
				if (TcpEndPoint.TryParse(m.Groups[1].Value + ":" + m.Groups[2].Value, ref p) <= 2 && p != null)
					ep = p;
			}
			head = tp.ClientConnect.Client.RemoteEndPoint + " Http " + ep;
			if (LogFile != null)
				WriteLog(head);
			if (LogOut != null)
				WriteLine(head);
			return ep;
		}

		private static IPEndPoint ProxyFtp(TcpProxy tp, IPEndPoint ep, string head)
		{
			var h = EventFtp;
			if (h != null)
			{
				if (LogFile != null)
					WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " " + h.Method);
				h(tp.ClientConnect, tp);
				return null;
			}

			var m = Regex.Match(head, @"^.+-Via: ([^\:]+)\:([0-9]+)$", RegexOptions.Compiled | RegexOptions.Multiline);
			if (m.Success)
			{
				var p = ep;
				if (TcpEndPoint.TryParse(m.Groups[1].Value + ":" + m.Groups[2].Value, ref p) <= 2 && p != null)
					ep = p;
			}
			head = tp.ClientConnect.Client.RemoteEndPoint + " Ftp " + ep;
			if (LogFile != null)
				WriteLog(head);
			if (LogOut != null)
				WriteLine(head);
			return ep;
		}

		private static IPEndPoint ProxyOther(TcpProxy tp, IPEndPoint ep, string head)
		{
			var h = EventOther;
			var p = tp.ClientConnect.Client.RemoteEndPoint as IPEndPoint;
			if (!Monitor.TryEnter(PS, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(PS);
			try
			{
				if (PS.TryGetValue(p.Address.ToString(), out p) && p != null)
					ep = p;
				else
					p = null;
			}
			finally
			{
				Monitor.Exit(PS);
			}
			if (p == null)
			{
				if (h != null)
				{
					if (LogFile != null)
						WriteLog(tp.ClientConnect.Client.RemoteEndPoint + " " + h.Method);
					h(tp.ClientConnect, tp);
					return null;
				}

				var m = Regex.Match(head, @"^.+-Via: ([^\:]+)\:([0-9]+)$", RegexOptions.Compiled | RegexOptions.Multiline);
				if (m.Success)
				{
					p = ep;
					if (TcpEndPoint.TryParse(m.Groups[1].Value + ":" + m.Groups[2].Value, ref p) <= 2 && p != null)
						ep = p;
				}
			}
			head = tp.ClientConnect.Client.RemoteEndPoint + " Proxy " + p;
			if (LogFile != null)
				WriteLog(head);
			if (LogOut != null)
				WriteLine(head);
			return ep;
		}

		/// <summary/>
		public static int Proxy(params string[] args)
		{
			if (args != null && args.Length > 0)
			{
				for (var i = args.Length - 1; i >= 0; i--)
					Environment.SetEnvironmentVariable("/" + i, args[i] ?? string.Empty, EnvironmentVariableTarget.Process);
				Environment.SetEnvironmentVariable("/-", args.Length.ToString(), EnvironmentVariableTarget.Process);
			}
			else
			{
				Environment.SetEnvironmentVariable("/-", "0", EnvironmentVariableTarget.Process);
			}
			if (Environment.UserInteractive)
			{
				var ts = GetListeners(args);
				if (ts == null)
					return Environment.ExitCode;

				Console.WriteLine("Enter [quit] to stop.");
				while (string.Compare(Console.ReadLine(), "quit", StringComparison.OrdinalIgnoreCase) != 0)
					Console.WriteLine("Enter [quit] to stop.");
				foreach (var tl in ts)
					tl.Stop();
				return Environment.ExitCode;
			}

			Service.Run(new Service());
			return Environment.ExitCode;
		}

		/// <summary/>
		public static int Proxy(TcpListener server, IPEndPoint proxy, IPEndPoint http, IPEndPoint ftp, IPEndPoint other)
		{
			PWH.WaitOne();
			if (LogOut != null)
				WriteLine(DateTime.Now + " **** Waiting at " + server.Server.LocalEndPoint);
			try
			{
				server.BeginAcceptTcpClient(EndAcceptTcpClient, new object[] { server, proxy, http, ftp, other, });
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
				return -1;
			}

			return 0;
		}

		private static void EndAcceptTcpClient(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(EndAcceptTcpClient, result))
				return;

			var i = 0;
			var os = result.AsyncState as object[];
			var ts = os[i++] as TcpListener;
			var p0 = os[i++] as IPEndPoint;
			var p1 = os[i++] as IPEndPoint;
			var p2 = os[i++] as IPEndPoint;
			var ps = new object[os.Length];
			var tp = new TcpProxy(null);
			try
			{
				tp.ClientConnect = ts.EndAcceptTcpClient(result);
				if (LogOut != null)
					WriteLine("Accept " + tp.ClientConnect.Client.RemoteEndPoint);
			}
			catch (Exception x)
			{
				tp = null;
				if (LogError != null)
					WriteError(x);
			}
			if (LogFile != null)
				WriteLog("Accept " + tp.ClientConnect.Client.RemoteEndPoint);
			Proxy(ts, p0, p1, p1, p2);
			if (tp == null || tp.ClientConnect == null)
				return;

			try
			{
				tp.ClientStream = tp.ClientConnect.GetStream();
				tp.ClientBuffer = new byte[BUFFER];
				os.CopyTo(ps, 0);
				ps[0] = tp;
				tp.ClientStream.BeginRead(tp.ClientBuffer, 0, BUFFER, EndRead, ps);
				return;
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
			}
			tp.Dispose(false);
		}

		private static void EndRead(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(EndRead, result))
				return;

			var i = 0;
			var t = string.Empty;
			var os = result.AsyncState as object[];
			var tp = os[i++] as TcpProxy;
			var pp = os[i++] as IPEndPoint;
			var hp = os[i++] as IPEndPoint;
			var fp = os[i++] as IPEndPoint;
			var op = os[i++] as IPEndPoint;
			try
			{
				i = tp.ClientStream.EndRead(result);
			}
			catch (Exception x)
			{
				i = 0;
				if (LogError != null)
					WriteError(x);
			}
			if (i == 0)
			{
				if (LogOut != null)
					WriteLine("No begin data.");
				tp.Dispose(false);
				return;
			}

			tp.ClientCount += i;
			i = (int)tp.ClientCount;
			t = Encoding.UTF8.GetString(tp.ClientBuffer, 0, i);
			if (LogOut != null)
				WriteLine(t);
			if (i < byte.MaxValue && ((t[0] >= 'A' && t[0] <= 'Z') || (t[0] >= 'a' && t[0] <= 'z')) && t.IndexOf(' ') < 0)
			{
				try
				{
					tp.ClientStream.BeginRead(tp.ClientBuffer, i, BUFFER - i, EndRead, os);
				}
				catch (Exception x)
				{
					if (LogError != null)
						WriteError(x);
					tp.Dispose(false);
				}
				return;
			}

			#region choose

			if (t[0] < 'A' || (t[0] > 'Z' && t[0] < 'a') || t[0] > 'z')//其它代理
			{
				op = ProxyOther(tp, op, t);
				if (op == null)
					return;

				pp = hp = fp = op;
			}
			else if (t.StartsWith(WebRequestMethods.Http.Connect + " ", StringComparison.OrdinalIgnoreCase) ||
				TcpEndPoint.IsMatch(pp, t)) // Http 代理
			{
				if (t.IndexOf("\r\n") > 0 && t.IndexOf("\r\n\r\n") < 0)
				{
					if (i > BUFFER / 2)
					{
						if (LogFile != null)
							WriteLog("Invalid Data" + Environment.NewLine + t);
						tp.Dispose(false);
						return;
					}

					try
					{
						tp.ClientStream.BeginRead(tp.ClientBuffer, i, BUFFER - i, EndRead, os);
					}
					catch (Exception x)
					{
						if (LogError != null)
							WriteError(x);
						tp.Dispose(false);
					}
					return;
				}

				pp = ProxyConnect(tp, pp, t);
				if (pp == null)
					return;

				i = 0;
				hp = fp = op = pp;
			}
			else if (t.StartsWith(WebRequestMethods.Http.Get + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Http.Head + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Http.MkCol + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Http.Post + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Http.Put + " ", StringComparison.OrdinalIgnoreCase) ||
				TcpEndPoint.IsMatch(hp, t)) // Ftp代理
			{
				if (t.IndexOf("\r\n") > 0 && t.IndexOf("\r\n\r\n") < 0)
				{
					if (i > BUFFER / 2)
					{
						if (LogFile != null)
							WriteLog("Invalid Data" + Environment.NewLine + t);
						tp.Dispose(false);
						return;
					}

					try
					{
						tp.ClientStream.BeginRead(tp.ClientBuffer, i, BUFFER - i, EndRead, os);
					}
					catch (Exception x)
					{
						if (LogError != null)
							WriteError(x);
						tp.Dispose(false);
					}
					return;
				}

				hp = ProxyHttp(tp, hp, t);
				if (hp == null)
					return;

				pp = fp = op = hp;
			}
			else if (t.StartsWith(WebRequestMethods.Ftp.AppendFile + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.DeleteFile + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.DownloadFile + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.GetDateTimestamp + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.GetFileSize + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.ListDirectory + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.ListDirectoryDetails + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.MakeDirectory + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.PrintWorkingDirectory + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.RemoveDirectory + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.Rename + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.UploadFile + " ", StringComparison.OrdinalIgnoreCase) ||
				t.StartsWith(WebRequestMethods.Ftp.UploadFileWithUniqueName + " ", StringComparison.OrdinalIgnoreCase) ||
				TcpEndPoint.IsMatch(fp, t))
			{
				fp = ProxyOther(tp, fp, t);
				if (fp == null)
					return;

				pp = hp = op = fp;
			}
			else//其它代理
			{
				op = ProxyOther(tp, op, t);
				if (op == null)
					return;

				pp = hp = fp = op;
			}

			#endregion

			if (tp.ClientConnect == null || tp.ClientStream == null)
			{
				tp.Dispose(false);
				return;
			}

			if (pp == null)
			{
				if (LogOut != null)
					WriteLine("Not support");
				tp.Dispose(false);
				return;
			}

			tp.RemoteConnect = new TcpClient();
			tp.RemoteConnect.BeginConnect(pp.Address, pp.Port, EndConnect, new object[] { tp, i, t });
		}

		private static void EndConnect(IAsyncResult result)
		{
			if (Thread.CurrentThread.IsThreadPoolThread && Fiber.QueueUserAsyncCallback(EndConnect, result))
				return;

			var i = 0;
			var os = result.AsyncState as object[];
			var tp = os[i++] as TcpProxy;
			i = (int)os[i++];
			try
			{
				tp.RemoteConnect.EndConnect(result);
				tp.RemoteStream = tp.RemoteConnect.GetStream();
				if (i > 0)
					tp.RemoteStream.Write(tp.ClientBuffer, 0, i);
				else
					tp.ClientStream.Write(H200, 0, H200.Length);
				tp.EndClientRead(null);
				tp.EndRemoteRead(null);
				return;
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
			}
			tp.Dispose(false);
		}

		/// <summary/>
		public static void WriteLog(string content)
		{
			if (string.IsNullOrWhiteSpace(LogFile))
				return;

			if (!Monitor.TryEnter(PS, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(PS);
			try
			{
				File.AppendAllText(LogFile, DateTime.Now + " " + content + Environment.NewLine);
			}
			catch (Exception x)
			{
				if (LogError != null)
					WriteError(x);
				LogFile = null;
			}
			finally
			{
				Monitor.Exit(PS);
			}
		}

		/// <summary/>
		public static void WriteLine(string content)
		{
			if (LogOut == null)
				Console.WriteLine(content);
			else
				LogOut.WriteLine(content);
		}

		/// <summary/>
		public static void WriteError(string error)
		{
			if (LogError == null)
				Console.WriteLine(error);
			else
				LogError.WriteLine(error);
		}

		/// <summary/>
		public static void WriteError(Exception error)
		{
			if (LogError == null)
				Console.WriteLine(error.Message);
			else
				LogError.WriteLine(error.Message);
		}
	}
}
